// Copyright 2014 The Go Circuit Project
// Use of this source code is governed by the license for
// The Go Circuit Project, found in the LICENSE file.
//
// Authors:
//   2014 Chris Monson <chris@gocircuit.org>

package ripple

import (
	"fmt"
	"log"
	"math/rand"
	"path"
	"strings"
	"sync"
	"time"

	"circuit/kit/rh"
	"circuit/kit/rh/ns"
)

const EndPointDirHelpFmt = `
  This is the directory for a %s ripple, named %q, with QID ⟨Q%016x⟩.

  SYNOPSIS

  cat help   # Read this help screen.
  cat new    # Create a new unique %s and output its name.

  Making a new child subdirectory will create a %s.

`

type EndPointDir struct {
	// immutable
	cmd   map[string]rh.FID
	kind  string
	name  string
	help  string
	gtm   time.Time
	q     rh.Q
	clunk func()
	newEP func(name string) rh.FID

	// mutable
	sync.Mutex
	atm    time.Time
	ripple map[string]RippleFID
}

func NewEndPointDir(kind, name string, newEndPoint func(name string) rh.FID) (*EndPointDir, rh.FID) {
	now := time.Now()
	id := uint64(rand.Int63())
	htext := fmt.Sprintf(EndPointDirHelpFmt, kind, path.Join("/", name), id, kind, kind)

	e := &EndPointDir{
		kind:  kind,
		name:  name,
		help:  htext,
		newEP: newEndPoint,
		gtm:   now,
		q: rh.Q{
			ID:  id,
			Ver: 1,
		},
		atm: now,
		// ripple holds the new mutexes that get created under this directory.
		ripple: make(map[string]RippleFID),
	}

	// cmd holds the commands that are fixed.
	e.cmd = map[string]rh.FID{
		"help": NewHelp(htext),
		"new": NewGenericReader("new", func() ([]byte, error) {
			name, _, err := e.MakeUniqueEndPoint()
			if err != nil {
				return nil, err
			}
			return []byte(name), nil
		}),
	}

	return e, e.Walk()
}

func (e *EndPointDir) Help() string {
	return e.help
}

func (e *EndPointDir) toc() (toc rh.DirChunk) {
	defer un(lock(e))
	// List fixtures like "help"
	for _, f := range e.cmd {
		dir, err := f.Stat()
		if err != nil {
			panic(err)
		}
		toc = append(toc, dir)
	}
	// List sub-"kind"s that are created (e.g., mutexes)
	// These are instances created by mkdir.
	for _, f := range e.ripple {
		dir, err := f.Stat()
		if err != nil {
			panic(err)
		}
		toc = append(toc, dir)
	}
	return toc
}

func (e *EndPointDir) Dump(fix string) {
	defer un(lock(e))
	for name, r := range e.ripple {
		fmt.Printf("%s %s\n", fix, name)
		rpl, ok := r.(*epFID)
		if !ok {
			continue
		}
		rpl.e.Dump(fix + " " + name)
	}
}

func (e *EndPointDir) ClunkWith(clunk func()) {
	e.clunk = clunk
}

func (e *EndPointDir) scrub(name string, r RippleFID) {
	defer un(lock(e))

	x, ok := e.ripple[name]
	if !ok {
		return
	}
	if r != x {
		return
	}
	delete(e.ripple, name)
}

func (e *EndPointDir) Walk() rh.FID {
	return &epFID{e: e}
}

// MakeUniqueEndPoint creates a new endpoint with a new name and returns it.
func (e *EndPointDir) MakeUniqueEndPoint() (string, rh.FID, error) {
	defer un(lock(e))

	for i := 0; i < 10; i++ {
		// Names starting with _ are reserved, so this should never collide
		// with user-created directories.
		candidate := fmt.Sprintf("_%08x", rand.Int63())
		fid, err := e.makeEndPointHelper(candidate)
		if err == nil {
			return candidate, fid, err
		}
	}
	log.Printf("Failed to find unique name after multiple attempts")
	return "", nil, rh.ErrExist
}

// MakeEndPoint
func (e *EndPointDir) MakeEndPoint(name string) (rh.FID, error) {
	defer un(lock(e))
	return e.makeEndPointHelper(name)
}

// makeEndPointHelper does not acquire a lock. It assumes that the caller holds
// the lock.
func (e *EndPointDir) makeEndPointHelper(name string) (rh.FID, error) {
	if !e.approve(name) {
		log.Printf("Error: %s name %q not approved", e.kind, name)
		return nil, rh.ErrExist
	}
	fid := e.newEP(name)
	e.add(name, fid)
	return fid, nil
}

func (e *EndPointDir) add(name string, r RippleFID) {
	if !e.approve(name) {
		panic("u")
	}
	e.ripple[name] = ns.NewFID(name, r)
}

func (e *EndPointDir) approve(name string) bool {
	_, ok := e.ripple[name]
	if ok {
		return false
	}
	_, ok = e.cmd[name]
	if ok {
		return false
	}
	return true
}

// epFID is the RH-facing side of an endpoint directory
type epFID struct {
	rh.AnchoredFID
	rh.DontWriteFID

	e *EndPointDir
}

func (fid *epFID) Clunk() {
	// Don't do anything.
	// When all pointers to this FID are gone, it will scrub itself from its parent automatically.
}

func (fid *epFID) String() string {
	return fmt.Sprintf("%s dir %p", fid.e.kind, fid)
}

func (fid *epFID) Q() rh.Q {
	return fid.e.q
}

// Open is nop.
func (fid *epFID) Open(rh.Flag, rh.Intr) error {
	return nil
}

// Create allows creation of subdirectories, which are ripples of the specified kind (e.g., mutex).
func (fid *epFID) Create(name string, _ rh.Flag, mode rh.Mode, _ rh.Perm) (rh.FID, error) {
	defer func() {
		fid.e.Dump("")
	}()
	if mode.Attr != rh.ModeDir {
		return nil, rh.ErrPerm
	}
	// We reserve all names starting with _ for internal use.
	if strings.HasPrefix(name, "_") {
		log.Printf("Illegal directory name %q. Names starting with _ are reserved.", name)
		return nil, rh.ErrExist
	}
	fid2, err := fid.e.MakeEndPoint(name)
	if err != nil {
		return nil, err
	}
	return fid2, nil
}

func (fid *epFID) Stat() (*rh.Dir, error) {
	defer un(lock(fid.e))

	now := time.Now()
	return &rh.Dir{
		Q:      fid.e.q,
		Mode:   rh.Mode{Attr: rh.ModeDir},
		Perm:   0777,
		Atime:  now, //fid.e.atm,
		Mtime:  now, //fid.e.atm,
		Name:   fid.e.name,
		Length: int64(len(fid.e.cmd) + len(fid.e.ripple)),
		Uid:    uid(),
		Gid:    gid(),
	}, nil
}

//
func (fid *epFID) Read(offset int64, count int, _ rh.Intr) (rh.Chunk, error) {
	var toc rh.DirChunk
	toc = fid.e.toc()
	if count == 0 {
		count = len(toc)
	}
	if int(offset) >= len(toc) {
		return nil, nil
	}
	toc = toc[offset:min(len(toc), int(offset)+count)]
	return toc, nil
}

func (fid *epFID) Walk(wname []string) (rh.FID, error) {
	if len(wname) == 0 {
		return fid.e.Walk(), nil
	}
	if len(wname) > 1 {
		return nil, rh.ErrClash
	}
	// command?
	if cmdFID, ok := fid.e.cmd[wname[0]]; ok {
		return cmdFID.Walk(nil)
	}
	// ripple?
	if rFID, ok := fid.e.ripple[wname[0]]; ok {
		return rFID.Walk(nil)
	}
	return nil, rh.ErrNotExist
}
